package net.lucene.chapter5.analyzer;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;

import java.io.IOException;
import java.io.StringReader;
//@formatter:off
/**
 * 【第五章】自定义分词器
 * ------------------------------------------------------------
 *
 * 推荐的四大分词器：SimpleAnalyzer、StopAnalyzer、WhitespaceAnalyzer、StandardAnalyzer
 * 四大分词器有一个共同的抽象类ReusableAnalyzerBase，此类有一个方法public final TokenStream tokenStream()，即分词的一个流
 * 假设有这样一个文本“how are you tank you”，实际上他是一个java.io.Reader传进分词器中
 * Lucene分词器处理完毕后，会把整个分词转换为TokenStream，这个TokenStream就保留所有分词的信息
 * TokenStream有两个实现类，分别是Tokenizer和TokenFilter
 * @see org.apache.lucene.analysis.Tokenizer        用于将一组数据划分为独立的词汇单元（即一个一个单词）
 * @see org.apache.lucene.analysis.TokenFilter      过滤词汇单元
 *
 * ------------------------------------------------------------
 * 分词流程
 *
 * 1) 将一组数据流java.io.Reader交给Tokenizer，由其将数据转换为一个个的词汇单元
 * 2) 通过大量的TokenFilter对已经分好词的数据进行过滤操作，最后产生TokenStream
 * 3) 通过TokenStream完成索引的存储
 *
 * ------------------------------------------------------------
 * Tokenizer的一些子类
 *
 * @see org.apache.lucene.analysis.KeywordTokenizer             不分词，传什么就索引什么
 * @see org.apache.lucene.analysis.standard.StandardTokenizer   标准分词，它有一些智能的分词操作，诸如将“chemnk@163.com”中的“163.com”当作一个分词流
 * @see org.apache.lucene.analysis.CharTokenizer                针对字符进行控制的，它还有两个子类WhitespaceToken和LetterToken
 * @see org.apache.lucene.analysis.WhitespaceTokenizer          使用空格进行分词，诸如将'Thank you,I am jadyer'会被分为4个词
 * @see org.apache.lucene.analysis.LetterTokenizer              基于文本单词的分词，它会根据标点符号来分词，诸如将'Thank you,I am jadyer'会被分为5个词
 * @see org.apache.lucene.analysis.LowerCaseTokenizer           它是LetterTokenizer的子类，它会根据数据转换为小写并分词
 *
 * ------------------------------------------------------------
 * TokenFilter的一些子类
 *
 * @see org.apache.lucene.analysis.StopFilter                   他会停用一些词汇单元
 * @see org.apache.lucene.analysis.LowerCaseFilter              将数据转换为小写
 * @see org.apache.lucene.analysis.standard.StandardFilter      对标准输出流作一些控制
 * @see org.apache.lucene.analysis.PorterStemFilter             对词元(token)进行处理，交由Stemming和Lemmatization将词汇转换为词根
 *
 *
 * eg:'how are you thank you'会被分词为'how','are','you','thank','you'合计5个语汇单元，那么应该保存什么东西,才能使以后在需要
 * 还原数据时保证正确的还原呢???其实主要保存三个东西,如下所示
 *
 * CharTermAttribute(Lucene3.5以前叫TermAttribute),OffsetAttribute,PositionIncrementAttribute
 * @see org.apache.lucene.analysis.tokenattributes.CharTermAttribute            保存相应的词汇,这里保存的就是'how','are','you','thank','you'
 * @see org.apache.lucene.analysis.tokenattributes.OffsetAttribute              保存各词汇之间的偏移量(大致理解为顺序),比如'how'的首尾字母偏移量为0和3,'are'为4和7,'thank'为12和17
 * @see org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute   保存词与词之间的位置增量,比如'how'和'are'增量为1,'are'和'you'之间的也是1,'you'和'thank'的也是1，但假设'are'是停用词(StopFilter的效果),那么'how'和'you'之间的位置增量就变成了2，当我们查找某一个元素时,Lucene会先通过位置增量来取这个元素,但如果两个词的位置增量相同,会发生什么情况呢，假设还有一个单词'this',它的位置增量和'how'是相同的,那么当我们在界面中搜索'this'时，也会搜到'how are you thank you',这样就可以有效的做同义词了,目前非常流行的一个叫做WordNet的东西,就可以做同义词的搜索
 *
 *
 * @author Barudisshu
 */
//@formatter:on
public class HelloCustomAnalyzer {

    /**
     * 查看分词信息
     *
     * @param txt        待分词的字符串
     * @param analyzer   所使用的分词器
     * @param displayAll 是否显示所有的分词信息
     * @see org.apache.lucene.analysis.TokenStream 还有两个属性，分别为FlagsAttribute和PayloadAttribute，都是开发时用的
     * @see org.apache.lucene.analysis.tokenattributes.FlagsAttribute   标注位属性
     * @see org.apache.lucene.analysis.tokenattributes.PayloadAttribute 做负载的属性，用来检测是否已经超过负载，超过则可以决定是否停止搜索等
     */
    public static void displayTokenInfo(String txt, Analyzer analyzer, boolean displayAll) {

        // 第一个参数没有任何意义,可以随便传一个值,它只是为了显示分词
        // 这里就是使用指定的分词器将'txt'分词,分词后会产生一个TokenStream(可将分词后的每个单词理解为一个Token)
        final TokenStream stream = analyzer.tokenStream("此参数无意义", new StringReader(txt));
        /*
           用于查看每一个词汇单元的信息，即分词的每一个元素
           这里创建的属性会被添加到TokenStream流中，并随着TokenStream而增加(此属性就是用来装载每个Token，即分词后的每个单词)
           当调用TokenStream.incrementToken()时，就会指向到这个单词流中的第一个单词，即此属性代表的就是分词后的第一个单词
         */
        CharTermAttribute cta = stream.addAttribute(CharTermAttribute.class);
        // 用于查看位置增量(指的是词汇单元之间的距离，可理解为元素与元素之间的空格，即间隔的单元数)
        PositionIncrementAttribute pia = stream.addAttribute(PositionIncrementAttribute.class);
        // 用于查看每个词汇单元的偏移量
        OffsetAttribute oa = stream.addAttribute(OffsetAttribute.class);
        // 用于查看使用分词器的类型信息
        TypeAttribute ta = stream.addAttribute(TypeAttribute.class);

        try {
            if (displayAll) {
                // 等价于while(stream.incrementToken())
                for (; stream.incrementToken(); ) {
                    // 输出每个分词
                    System.out.println(ta.type() + " " + pia.getPositionIncrement() + " [" + oa.startOffset() + "-" + oa.endOffset() + "] [" + cta + "]");
                }
            } else {
                System.out.println();
                while (stream.incrementToken()) {
                    System.out.print("[" + cta + "]");
                }
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}
